package fr.uvsq.isty.bdh.hchess.gui.style;

import fr.uvsq.isty.bdh.hchess.util.GlobalConfig;
import fr.uvsq.isty.bdh.hchess.util.ConfigFile;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 * Singleton which contains the current skin of the board.
 * It allows to retrieve a picture with the correct skin.
 * 
 * @version 1.0
 * @author Florent Hemmi <florent.hemmi@gmail.com>
 */
public final class Style {
    
    /** Unique instance */
    private static Style instance = null;
    
    /** Name of the default skin */
    private static final String DEFAULT_STYLE = "basic";
    /** Path where the class can find all the skin */
    private static final String RESOURCES = "styles/";
    /** Skin configuration file */
    private static final String OPTION_FILE = "info.ini";
    
    /** Skin section name in the configuration file */
    private static final String STYLE_SECTION = "style";
    /** Skin option name in the configuration file */
    private static final String STYLE_OPTION = "name";
    
    /** Author section name */
    private static final String AUTHOR_SECTION = "author";
    /** Author option name */
    private static final String AUTHOR_OPTION = "name";
    
    /** Paddings section name */
    private static final String PADDINGS_SECTION = "paddings";
    /** Padding x option name */
    private static final String PADDING_X_OPTION = "x";
    /** Padding y option name */
    private static final String PADDING_Y_OPTION = "y";
    /** Default padding to use if there is no padding defined in the config file */
    private static final int DEFAULT_PADDING = 5;
    
    /** Current style name to use */
    private String currentStyleName;
    
    /** All resources of the current style.
     Load each time the skin change.
     */
    private List<Image> images;
    /** All resources name of the current style. */
    private List<String> imagesName;
    /** Default paddings if not found in the configuration file */
    private int[] paddings = {DEFAULT_PADDING,DEFAULT_PADDING};
    
    /** Private constructor for the initialization */
    private Style() throws IOException {
        this.images = new ArrayList<Image>();
        this.imagesName = new ArrayList<String>();
        this.currentStyleName = loadLastStyle();
        loadResources();
    }
    
    /**
     * Call this method to retrieve the instance of the object.
     * In fact, constructor cannot be called directly.
     * 
     * @return An instance of the object (the same as before if already created)
     */
    public static Style getInstance() throws IOException {
        if (instance == null) {
            instance = new Style();
        }
        return instance;
    }
    
    /**
     * This method can only be used internally.
     * 
     * @return Full relative path of the skin directory
     */
    private String getStyleDirectory() {
        StringBuilder dir = new StringBuilder(RESOURCES);
        return dir.append(currentStyleName).append("/").toString();
    }

    /**
     * See @return.
     * 
     * @return Name of the current style
     */
    public String getCurrentStyleName() {
        return currentStyleName;
    }
    
    /**
     * See @param.
     * 
     * @param currentStyleName Change the name of the current style
     */
    public void setCurrentStyleName(String currentStyleName) throws IOException {
        if (!this.currentStyleName.equals(currentStyleName)) {
            this.currentStyleName = currentStyleName;
            saveLastStyle();
            loadResources();
        }
    }
    
    /**
     * List all skin name available.
     * @return String array of skin name.
     * @throws IOException
     */
    public List<String> list() throws IOException {
        List<String> styleList = new ArrayList<String>();
        
        File dir = new File(RESOURCES);
        if (!dir.exists()) {
            throw new IOException("Cannot load the root style directory.");
        }
        
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                styleList.add(f.getName());
            }
        }
        return styleList;
    }
    
    /**
     * Load all necessary ressources one time when the skin is updated.
     * 
     * @throws IOException 
     */
    private void loadResources() {
        // Load all other informations in the style config.
        loadStyleInfo();
        
        // Empty all available resources.
        images.clear();
        imagesName.clear();
        
        File dir = new File(this.getStyleDirectory());
        if (dir.exists()) {
            for (String f : dir.list()) {
                if (f.length()>4 && f.substring(f.length()-4,f.length()).equals(".png")) {
                    // Remove the extension.
                    imagesName.add(f.substring(0, f.length()-4));
                    try {
                        // Read the image.
                        images.add(ImageIO.read(new File(this.getStyleDirectory()+f)));
                    } catch (IOException ex) {
                        images.add(null);
                    }
                }
            }
        }
    }
    
    /**
     * 
     * @param resource String which describe the ressource (i.e.: b_pawn)
     * @return Resource ID or -1 if not available
     */
    private int getIdOfResource(String resource) {
        for (int i=0; i<imagesName.size(); i++) {
            if (imagesName.get(i).equals(resource))
                return i;
        }
        return -1;
    }
    
    /**
     * 
     * @return Image of the board
     */
    public Image getBoardImage() {
        int id = getIdOfResource("board");
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @return the default for the style
     */
    public Image getBGImage() {
        int id;
        id = getIdOfResource("bg");
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @param blackCase if the case is black
     * @return an image corresponding to the wanted case
     */
    public Image getCaseImage(boolean blackCase) {
        int id;
        if(blackCase) {
            id = getIdOfResource("blackCase");
        }
        else {
            id = getIdOfResource("whiteCase");
        }
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @param descImg image description
     * @return an image corresponding to a move
     */
    public Image getMoveImage(String descImg) {
        StringBuilder resource = new StringBuilder("move_");
        resource.append(descImg);
        int id = getIdOfResource(resource.toString());
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @param descImg image description
     * @return an image corresponding to the end game
     */
    public Image getEndImage(String descImg) {
        StringBuilder resource = new StringBuilder("end_");
        resource.append(descImg);
        int id = getIdOfResource(resource.toString());
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @param blackPlayerTurn if it's black turn
     * @return an image corresponding
     */
    public Image getPlayerTurnImage(boolean blackPlayerTurn) {
        int id;
        if (blackPlayerTurn) {
            id = getIdOfResource("blackTurn");
        }
        else {
            id = getIdOfResource("whiteTurn");
        }
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * 
     * @param piece String which describe the piece (i.e.: pawn)
     * @param isBlack true if the piece is black
     * @return An image
     */
    public Image getPieceImage(String piece, boolean isBlack) {
        StringBuilder resource = new StringBuilder("");
        
        if (isBlack) resource.append("b");
        else resource.append("w");
        
        resource.append("_").append(piece.toLowerCase());
        
        int id = getIdOfResource(resource.toString());
        if (id < 0) return null;
        return images.get(id);
    }
    
    /**
     * Store current style in an INI file.
     */
    private void saveLastStyle() {
        try {
            ConfigFile ini = GlobalConfig.getInstance();
            ini.put(STYLE_SECTION, STYLE_OPTION, currentStyleName);
        } catch (IOException ex) {}
    }
    
    /**
     * Load style from an INI file.
     * 
     * @return Last Style loaded
     */
    private String loadLastStyle() {
        try {
            ConfigFile ini = GlobalConfig.getInstance();
            return ini.get(STYLE_SECTION, STYLE_OPTION, DEFAULT_STYLE);
        } catch (IOException ex) {}
        return DEFAULT_STYLE;
    }
    
    /**
     * 
     * @return config filename
     */
    public String getConfigFilename() {
        return RESOURCES+currentStyleName+"/"+OPTION_FILE;
    }
    
    /**
     * Retrieve all informations store in the configuration file.
     */
    private void loadStyleInfo() {
        try {
            ConfigFile ini = new ConfigFile(new File(this.getConfigFilename()));
            paddings[0] = ini.getInt(PADDINGS_SECTION, PADDING_X_OPTION, DEFAULT_PADDING);
            paddings[1] = ini.getInt(PADDINGS_SECTION, PADDING_Y_OPTION, DEFAULT_PADDING);
        } catch (IOException ex) {}
    }
    
    /**
     * @param styleName internal style name
     * @return style name in the configuration file or default name
     */
    public String getFullStyleName(String styleName) {
        try {
            ConfigFile ini = new ConfigFile(new File(RESOURCES+styleName+"/"+OPTION_FILE));
            return ini.get(STYLE_SECTION, STYLE_OPTION, styleName);
        } catch (IOException ex) {}
        return styleName;
    }
    
    /**
     * 
     * @return the two paddings (x and y)
     */
    public int[] getPaddings() {
        return paddings;
    }
    
    /**
     * 
     * @return the author name
     */
    public String getAuthorName() {
        try {
            ConfigFile ini = new ConfigFile(new File(this.getConfigFilename()));
            return ini.get(AUTHOR_SECTION, AUTHOR_OPTION, "");
        } catch (IOException ex) {}
        return "";
    }
}
